#include "http_conn.h"

#include <mysql/mysql.h>
#include <fstream>

//定义http响应的一些状态信息
const char *ok_200_title = "OK";
const char *error_400_title = "Bad Request";
const char *error_400_form = "Your request has bad syntax or is inherently impossible to staisfy.\n";
const char *error_403_title = "Forbidden";
const char *error_403_form = "You do not have permission to get file form this server.\n";
const char *error_404_title = "Not Found";
const char *error_404_form = "The requested file was not found on this server.\n";
const char *error_500_title = "Internal Error";
const char *error_500_form = "There was an unusual problem serving the request file.\n";

locker m_lock;
map<string, string> users;

void http_conn::initmysql_result(connection_pool *connPool)
{
    //先从连接池中取一个连接
    MYSQL *mysql = NULL;
    connectionRAII mysqlcon(&mysql, connPool);

    //在user表中检索username，passwd数据，浏览器端输入
    if (mysql_query(mysql, "SELECT username,passwd FROM user"))
    {
        LOG_ERROR("SELECT error:%s\n", mysql_error(mysql));
    }

    //从表中检索完整的结果集
    MYSQL_RES *result = mysql_store_result(mysql);

    //返回结果集中的列数
    int num_fields = mysql_num_fields(result);

    //返回所有字段结构的数组
    MYSQL_FIELD *fields = mysql_fetch_fields(result);

    //从结果集中获取下一行，将对应的用户名和密码，存入map中
    while (MYSQL_ROW row = mysql_fetch_row(result))
    {
        string temp1(row[0]);
        string temp2(row[1]);
        users[temp1] = temp2;
    }
}

//对文件描述符设置非阻塞
int setnonblocking(int fd)
{
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

//将内核事件表注册读事件，ET模式，选择开启EPOLLONESHOT
void addfd(int epollfd, int fd, bool one_shot, int TRIGMode)
{
    epoll_event event;
    event.data.fd = fd;

    if (1 == TRIGMode)			//根据输入的参数选择不同的模式（ET模式（水平/边沿触发））
        event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
    else
        event.events = EPOLLIN | EPOLLRDHUP;

    if (one_shot)
        event.events |= EPOLLONESHOT;			//根据输入的参数选择是否 设置单触发模式
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setnonblocking(fd);			//设置非阻塞模式
}

//从内核事件表删除描述符
void removefd(int epollfd, int fd)
{
    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0);
    close(fd);
}

//将事件重置为EPOLLONESHOT
void modfd(int epollfd, int fd, int ev, int TRIGMode)
{
    epoll_event event;
    event.data.fd = fd;

    if (1 == TRIGMode)
        event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
    else
        event.events = ev | EPOLLONESHOT | EPOLLRDHUP;

    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}

int http_conn::m_user_count = 0;
int http_conn::m_epollfd = -1;

//关闭连接，关闭一个连接，客户总量减一
void http_conn::close_conn(bool real_close)
{
    if (real_close && (m_sockfd != -1))
    {
        printf("close %d\n", m_sockfd);
        removefd(m_epollfd, m_sockfd);
        m_sockfd = -1;
        m_user_count--;
    }
}

//初始化连接,外部调用 用来初始化套接字地址
void http_conn::init(int sockfd, const sockaddr_in &addr, char *root, int TRIGMode,
                     int close_log, string user, string passwd, string sqlname)
{
    m_sockfd = sockfd;
    m_address = addr;

    addfd(m_epollfd, sockfd, true, m_TRIGMode);				//以EPOLLONESHOT模式，和边沿触发模式 将sockfd加载到epoll树
    m_user_count++;				//用户数量加1

    //当浏览器出现连接重置时，可能是网站根目录出错或http响应格式出错或者访问的文件中内容完全为空
    doc_root = root;					//以传入的文件路径重连
    m_TRIGMode = TRIGMode;
    m_close_log = close_log;			//重置链接

    strcpy(sql_user, user.c_str());		//将传入的用户名、密码、SQL目录名赋值给http_conn类的成员变量
    strcpy(sql_passwd, passwd.c_str());
    strcpy(sql_name, sqlname.c_str());

    init();			//真实执行初始化的函数				
}

//初始化新接受的连接
//check_state默认为 分析请求行状态  （包括：请求行、标题、内容）
void http_conn::init()
{
    mysql = NULL;
    bytes_to_send = 0;
    bytes_have_send = 0;
    m_check_state = CHECK_STATE_REQUESTLINE;		//设置状态为 请求行
    m_linger = false;
    m_method = GET;									//请求方法为 GET方法
    m_url = 0;
    m_version = 0;
    m_content_length = 0;
    m_host = 0;
    m_start_line = 0;
    m_checked_idx = 0;
    m_read_idx = 0;
    m_write_idx = 0;
    cgi = 0;
    m_state = 0;
    timer_flag = 0;
    improv = 0;

    memset(m_read_buf, '\0', READ_BUFFER_SIZE);		//设置读写空间
    memset(m_write_buf, '\0', WRITE_BUFFER_SIZE);
    memset(m_real_file, '\0', FILENAME_LEN);
}

//从状态机，用于 按行读取 m_read_buf 中的数据
//返回值为行的读取状态，有LINE_OK,LINE_BAD,LINE_OPEN															//////////////////////////////////////////
http_conn::LINE_STATUS http_conn::parse_line()
{
    char temp;
    for (; m_checked_idx < m_read_idx; ++m_checked_idx)			//m_checked_idx：从状态机在 m_read_buf 中读取的位置			m_read_idx：m_read_buf 缓冲区中的数据最后一个字节的下一位（缓冲区中总数据的大小）
    {
        temp = m_read_buf[m_checked_idx];
        if (temp == '\r')
        {
            if ((m_checked_idx + 1) == m_read_idx)				//下一个读到了结尾
                return LINE_OPEN;								//返回    读取的行不完整
            else if (m_read_buf[m_checked_idx + 1] == '\n')
            {
                m_read_buf[m_checked_idx++] = '\0';
                m_read_buf[m_checked_idx++] = '\0';
                return LINE_OK;									//返回    完整读取一行
            }
            return LINE_BAD;									//若 '\r' 后面没有跟 '\n' , 则返回    报文语法有误
        }
        else if (temp == '\n')
        {
            if (m_checked_idx > 1 && m_read_buf[m_checked_idx - 1] == '\r')		//当前字节是 '\n'，判断其前一个是否是 '\r' ，若是将 '\r''\n'转换为'\0''\0'，并将m_checked_idx指向下一行的开头， 返回		完整读取一行
            {
                m_read_buf[m_checked_idx - 1] = '\0';
                m_read_buf[m_checked_idx++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
    }
    return LINE_OPEN;											//当前字节既不是 '\r' 也不是 '\n' ，表示接收数据不完整，需要继续接受，返回   LINE_OPEN
}

//循环读取客户数据，直到无数据可读或对方关闭连接
//非阻塞ET工作模式下，需要一次性将数据读完
bool http_conn::read_once()
{
    if (m_read_idx >= READ_BUFFER_SIZE)
    {
        return false;
    }
    int bytes_read = 0;

    //LT读取数据
    if (0 == m_TRIGMode)
    {
        bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0);
        m_read_idx += bytes_read;

        if (bytes_read <= 0)
        {
            return false;
        }

        return true;
    }
    //ET读数据
    else
    {
        while (true)
        {
            bytes_read = recv(m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0);			//从套接字接收数据，存储在 m_read_buf 缓冲区中；     READ_BUFFER_SIZE - m_read_idx表示缓冲区剩余的大小
            if (bytes_read == -1)
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK)												//非阻塞ET模式下，需要一次性将数据读完
                    break;
                return false;
            }
            else if (bytes_read == 0)
            {
                return false;
            }
            m_read_idx += bytes_read;
        }
        return true;
    }
}

//解析http请求行，获得请求方法，目标url及http版本号					请求行示例： GET /login.html HTTP/1.1
http_conn::HTTP_CODE http_conn::parse_request_line(char *text)
{
    m_url = strpbrk(text, " \t");						//查找请求行中最先含有空格或'\t'的位置并返回

	//如果没有空格或'\t' ，则返回 报文格式有误
    if (!m_url)
    {
        return BAD_REQUEST;
    }
    *m_url++ = '\0';									//将改为制改为'\0'，用于将前面数据取出
    char *method = text;								//取出数据，并通过与GET和POST比较，已确定请求方式
    if (strcasecmp(method, "GET") == 0)
        m_method = GET;
    else if (strcasecmp(method, "POST") == 0)
    {
        m_method = POST;
        cgi = 1;										//POST标志
    }
    else
        return BAD_REQUEST;								//若非GET、非POST，则返回报文格式有误

	//m_url此时跳过了第一个空格或\t字符，但不知道之后是否还有
	//将 m_url 向后偏移，通过查找，继续跳过空格和\t字符，指向请求资源的第一个字符   （即 /login.html）
    m_url += strspn(m_url, " \t");
	//使用与判断请求方式相同的逻辑，判断HTTP版本号  （即 HTTP/1.1）
    m_version = strpbrk(m_url, " \t");
    if (!m_version)
        return BAD_REQUEST;
    *m_version++ = '\0';
    m_version += strspn(m_version, " \t");
    if (strcasecmp(m_version, "HTTP/1.1") != 0)			//仅支持HTTP/1.1
        return BAD_REQUEST;
    if (strncasecmp(m_url, "http://", 7) == 0)			//对请求资源的前7个字符进行判断		主要是有些报文的请求资源中会带有 http:// ，这里对此情况单独处理
    {
        m_url += 7;
        m_url = strchr(m_url, '/');						//以 '/' 代替
    }

    if (strncasecmp(m_url, "https://", 8) == 0)			//同上，对 https:// ,进行单独处理
    {
        m_url += 8;
        m_url = strchr(m_url, '/');						//以 '/' 代替
    }

    if (!m_url || m_url[0] != '/')						//出去前两种情况，通常是 '/' 后跟要访问的资源
        return BAD_REQUEST;
    //当url为/时，显示判断界面
    if (strlen(m_url) == 1)								//长度为 1 必是 '/' 
        strcat(m_url, "judge.html");

	//请求行处理完毕，将主状态机转移处理请求头
    m_check_state = CHECK_STATE_HEADER;
    return NO_REQUEST;
}

//解析http请求的一个头部信息
http_conn::HTTP_CODE http_conn::parse_headers(char *text)
{
    if (text[0] == '\0')														//判断是空行还是请求头
    {
        if (m_content_length != 0)												//判断是GET请求还是POST请求
        {
            m_check_state = CHECK_STATE_CONTENT;								//如果是POST则需要跳转到消息体处理函数
            return NO_REQUEST;
        }
        return GET_REQUEST;
    }
    else if (strncasecmp(text, "Connection:", 11) == 0)							//解析请求头部连接字段
    {
        text += 11;
        text += strspn(text, " \t");											//跳过空格和\t字符
        if (strcasecmp(text, "keep-alive") == 0)
        {
            m_linger = true;													//如果是长连接，则将linger标志设置为true
        }
    }
    else if (strncasecmp(text, "Content-length:", 15) == 0)						//解析请求头部 内容长度 字段
    {
        text += 15;
        text += strspn(text, " \t");
        m_content_length = atol(text);
    }
    else if (strncasecmp(text, "Host:", 5) == 0)								//解析请求头部HOST字段
    {
        text += 5;
		text += strspn(text, " \t");
        m_host = text;
    }
    else
    {
        LOG_INFO("oop!unknow header: %s", text);
    }
    return NO_REQUEST;
}

//判断http请求是否被完整读入			解析消息体
http_conn::HTTP_CODE http_conn::parse_content(char *text)
{
    if (m_read_idx >= (m_content_length + m_checked_idx))				//判断buffer中是否读取了消息体
    {
        text[m_content_length] = '\0';
        //POST请求中最后为输入的用户名和密码
        m_string = text;
        return GET_REQUEST;
    }
    return NO_REQUEST;
}

//完成报文解析
http_conn::HTTP_CODE http_conn::process_read()
{
    LINE_STATUS line_status = LINE_OK;								//初始化从状态机的状态
    HTTP_CODE ret = NO_REQUEST;										//初始化HTTP请求的解析结果
    char *text = 0;

	//parse_line 为从状态机的具体实现
    while ((m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK) || ((line_status = parse_line()) == LINE_OK))		//parase_line()：从状态机，按行读取 m_read_buf 中的数据
    {
        text = get_line();											//text可以取出完整的行进行解析（因为从状态机已经将每一行末尾的"\r\n"变为了"\0\0"）

		//m_start_line是每一个数据行在 m_read_buf 中的起始位置
		//m_checked_idx表示从状态机在 m_read_buf 中读取的位置（下一行的开头）
        m_start_line = m_checked_idx;
        LOG_INFO("%s", text);

		//主状态机的三种状态转移逻辑
        switch (m_check_state)										//m_check_state：主状态机的状态（解析请求行、解析请求头、解析消息体）
        {
        case CHECK_STATE_REQUESTLINE:								//解析请求行
        {
            ret = parse_request_line(text);							//主状态机解析报文中的请求行数据 的函数
            if (ret == BAD_REQUEST)									//返回 HTTP请求报文有语法错误
                return BAD_REQUEST;
            break;
        }
        case CHECK_STATE_HEADER:									//解析请求头
        {
            ret = parse_headers(text);								//主状态机解析请求头数据 的函数
            if (ret == BAD_REQUEST)
                return BAD_REQUEST;
            else if (ret == GET_REQUEST)							//完整解析GET请求后，跳转到报文响应函数        GET_REQUEST：获取完整的HTTP请求
            {
                return do_request();								//报文响应函数
            }
            break;
        }
        case CHECK_STATE_CONTENT:									//解析消息体，仅用于解析POST请求    （因为GET请求没有消息体）
        {
            ret = parse_content(text);								//解析消息体的函数
            if (ret == GET_REQUEST)									//GET_REQUEST：获取完整的HTTP请求
                return do_request();								//完整解析POST请求后，跳转到报文响应函数
            line_status = LINE_OPEN;								//解析完消息体即完成报文解析，为避免再次进入循环，更新line_status	
            break;
        }
        default:
            return INTERNAL_ERROR;									//服务器内部错误
        }
    }
    return NO_REQUEST;
}

http_conn::HTTP_CODE http_conn::do_request()
{
    strcpy(m_real_file, doc_root);			//将初始化的 m_real_file 赋值为网站根目录
    int len = strlen(doc_root);
    //printf("m_url:%s\n", m_url);
    const char *p = strrchr(m_url, '/');												//找到 m_url 中 '/' 的位置

    //处理cgi  实现登录和注册校验
    if (cgi == 1 && (*(p + 1) == '2' || *(p + 1) == '3'))
    {

        //根据标志判断是登录检测还是注册检测
        char flag = m_url[1];

        char *m_url_real = (char *)malloc(sizeof(char) * 200);
        strcpy(m_url_real, "/");
        strcat(m_url_real, m_url + 2);
        strncpy(m_real_file + len, m_url_real, FILENAME_LEN - len - 1);
        free(m_url_real);

        //将用户名和密码提取出来
        //user=123&passwd=123
        char name[100], password[100];
        int i;
        for (i = 5; m_string[i] != '&'; ++i)									//以 & 为分隔符，前面的为用户名
            name[i - 5] = m_string[i];
        name[i - 5] = '\0';

        int j = 0;																//后面的是密码
        for (i = i + 10; m_string[i] != '\0'; ++i, ++j)
            password[j] = m_string[i];
        password[j] = '\0';

		//通过判断\后面的值判断是登录校验还是注册校验
        if (*(p + 1) == '3')													//*（p + 1）表示取出p后一位的数据
        {
            //如果是注册校验，先检测数据库中是否有重名的
            //没有重名的，进行增加数据
            char *sql_insert = (char *)malloc(sizeof(char) * 200);
            strcpy(sql_insert, "INSERT INTO user(username, passwd) VALUES(");
            strcat(sql_insert, "'");
            strcat(sql_insert, name);
            strcat(sql_insert, "', '");
            strcat(sql_insert, password);
            strcat(sql_insert, "')");

            if (users.find(name) == users.end())								//判断 map 中能否找到重复的用户名
            {
                m_lock.lock();
                int res = mysql_query(mysql, sql_insert);
                users.insert(pair<string, string>(name, password));				//无重复则插入数据
                m_lock.unlock();

                if (!res)
                    strcpy(m_url, "/log.html");									//校验成功，跳转至登录界面
                else
                    strcpy(m_url, "/registerError.html");						//校验失败，跳转至注册失败页面
            }
            else
                strcpy(m_url, "/registerError.html");
        }
        //如果是登录，直接判断
        //若浏览器端输入的用户名和密码在表中可以查找到，返回1，否则返回0
        else if (*(p + 1) == '2')
        {
            if (users.find(name) != users.end() && users[name] == password)		//若能找到重复的，且名字对应的键值等于输入的密码，跳转至登陆成功页面
                strcpy(m_url, "/welcome.html");
            else
                strcpy(m_url, "/logError.html");
        }
    }

	//如果请求资源为 /0，表示跳转到注册界面
    if (*(p + 1) == '0')
    {
        char *m_url_real = (char *)malloc(sizeof(char) * 200);
        strcpy(m_url_real, "/register.html");
        strncpy(m_real_file + len, m_url_real, strlen(m_url_real));

        free(m_url_real);
    }
    else if (*(p + 1) == '1')											//如果请求资源为 /1，表示跳转到登录界面
    {
        char *m_url_real = (char *)malloc(sizeof(char) * 200);
        strcpy(m_url_real, "/log.html");
        strncpy(m_real_file + len, m_url_real, strlen(m_url_real));		//将网站目录和 /log.html进行拼接，更新到 m_real_file 中

        free(m_url_real);
    }
    else if (*(p + 1) == '5')											//为 /5，表示跳转到图片
    {
        char *m_url_real = (char *)malloc(sizeof(char) * 200);
        strcpy(m_url_real, "/picture.html");
        strncpy(m_real_file + len, m_url_real, strlen(m_url_real));

        free(m_url_real);
    }
    else if (*(p + 1) == '6')											//为 /6，表示跳转到视频
    {
        char *m_url_real = (char *)malloc(sizeof(char) * 200);
        strcpy(m_url_real, "/video.html");
        strncpy(m_real_file + len, m_url_real, strlen(m_url_real));

        free(m_url_real);
    }
    else if (*(p + 1) == '7')											//为 /7，表示跳转到关注页面
    {
        char *m_url_real = (char *)malloc(sizeof(char) * 200);
        strcpy(m_url_real, "/fans.html");
        strncpy(m_real_file + len, m_url_real, strlen(m_url_real));

        free(m_url_real);
    }
    else
        strncpy(m_real_file + len, m_url, FILENAME_LEN - len - 1);			//如果以上均不符合，直接将 url 与 网站目录进行拼接

    if (stat(m_real_file, &m_file_stat) < 0)							//通过 stat函数 获取请求的资源文件 的信息，成功则将信息更新到 m_file_stat 结构体中；失败返回 NO_RESOURCE 状态，表示资源不存在
        return NO_RESOURCE;

    if (!(m_file_stat.st_mode & S_IROTH))								//判断文件的权限，是否可读，不可读则返回 FORBIDDEN_REQUEST 状态
        return FORBIDDEN_REQUEST;

    if (S_ISDIR(m_file_stat.st_mode))									//判断文件类型，如果是目录，则返回 BAD_REQUEST ，表示请求报文有误
        return BAD_REQUEST;

    int fd = open(m_real_file, O_RDONLY);								//以只读方式获取文件描述符，通过 mmap 将该文件映射到内存中
    m_file_address = (char *)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    close(fd);															//关闭文件描述符
    return FILE_REQUEST;												//表示请求文件存在，且可以访问
}
void http_conn::unmap()
{
    if (m_file_address)
    {
        munmap(m_file_address, m_file_stat.st_size);
        m_file_address = 0;
    }
}

//将响应报文发送给浏览器端												最终往浏览器回写的函数
bool http_conn::write()
{
    int temp = 0;

    if (bytes_to_send == 0)												//bytes_to_send：剩余要发送数据        若要发送数据长度为空，表示响应报文为空
    {
        modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);				//重新注册读事件
        init();
        return true;
    }

    while (1)
    {
        temp = writev(m_sockfd, m_iv, m_iv_count);						//将响应报文的 状态行、消息头、空行和响应正文 发送给浏览器端

		//若成功发送，则返回temp字节数，发送失败需要判断是缓冲区满了，还是发送错误
        if (temp < 0)
        {
            if (errno == EAGAIN)										//判断缓冲区是否满了
            {
                modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode);		//重新注册写事件
                return true;
            }
            unmap();													//若发送失败，且不是缓冲区问题，取消映射
            return false;
        }

		//更新剩余数据和已发送数据
        bytes_have_send += temp;
        bytes_to_send -= temp;
        if (bytes_have_send >= m_iv[0].iov_len)							//已发送数据和 保存状态行的缓冲区的数据长度 比较，判断写缓冲区的数据是否发送完				若超过缓冲区数据长度，开始发送文件映射区m_iv[1]的数据
        {
            m_iv[0].iov_len = 0;
            m_iv[1].iov_base = m_file_address + (bytes_have_send - m_write_idx);
            m_iv[1].iov_len = bytes_to_send;
        }
        else															//写缓冲区未发送完，改变写缓冲区写入位置和剩余长度，继续发送
        {
            m_iv[0].iov_base = m_write_buf + bytes_have_send;
            m_iv[0].iov_len = m_iv[0].iov_len - bytes_have_send;
        }

        if (bytes_to_send <= 0)											//判断是否发送完		若剩余数据<=0,表示数据发送完了
        {
            unmap();													//解除映射
            modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);			//重新注册监听事件

            if (m_linger)												//浏览器的请求为长连接
            {
                init();													//重新初始化HTTP对象
                return true;
            }
            else
            {
                return false;											//若为短连接直接退出
            }
        }
    }
}

//向写缓冲区写入数据
bool http_conn::add_response(const char *format, ...)
{
    if (m_write_idx >= WRITE_BUFFER_SIZE)											//如果写入内容超出 m_write_buf 大小则报错
        return false;
    va_list arg_list;																//定义可变参数列表
    va_start(arg_list, format);														//将可变参数列表 arg_list 初始化为传入参数 
    int len = vsnprintf(m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list);		//将数据 format 从可变参数列表写入写缓冲区，返回写入数据的长度
    if (len >= (WRITE_BUFFER_SIZE - 1 - m_write_idx))								//如果写入数据长度 超过  缓冲区剩余空间，则报错
    {
        va_end(arg_list);
        return false;
    }
    m_write_idx += len;																//更新 m_write_idx 的位置
    va_end(arg_list);																//清空可变参数列表

    LOG_INFO("request:%s", m_write_buf);

    return true;
}
bool http_conn::add_status_line(int status, const char *title)						//添加状态行
{
    return add_response("%s %d %s\r\n", "HTTP/1.1", status, title);
}
bool http_conn::add_headers(int content_len)										//添加消息报头 （具体添加：文本长度、连接状态和空行等）
{
    return add_content_length(content_len) && add_linger() &&
           add_blank_line();
}
bool http_conn::add_content_length(int content_len)									//添加响应报文的 文本长度
{
    return add_response("Content-Length:%d\r\n", content_len);
}
bool http_conn::add_content_type()													//添加文本类型，此处是 html
{
    return add_response("Content-Type:%s\r\n", "text/html");
}
bool http_conn::add_linger()														//添加状态行，通知浏览器端 是保持连接还是关闭
{
    return add_response("Connection:%s\r\n", (m_linger == true) ? "keep-alive" : "close");
}
bool http_conn::add_blank_line()													//添加空行
{
    return add_response("%s", "\r\n");
}
bool http_conn::add_content(const char *content)									//添加文本
{
    return add_response("%s", content);
}

//回写进程
bool http_conn::process_write(HTTP_CODE ret)
{
    switch (ret)
    {
    case INTERNAL_ERROR:											//内部错误，返回状态码 500
    {
        add_status_line(500, error_500_title);						//写回状态行
        add_headers(strlen(error_500_form));						//消息报头
        if (!add_content(error_500_form))
            return false;
        break;
    }
    case BAD_REQUEST:												//报文语法有误，返回 404
    {
        add_status_line(404, error_404_title);
        add_headers(strlen(error_404_form));
        if (!add_content(error_404_form))
            return false;
        break;
    }
    case FORBIDDEN_REQUEST:											//资源无访问权限，返回 403
    {
        add_status_line(403, error_403_title);
        add_headers(strlen(error_403_form));
        if (!add_content(error_403_form))
            return false;
        break;
    }
    case FILE_REQUEST:												//文件存在，返回 200
    {
        add_status_line(200, ok_200_title);
        if (m_file_stat.st_size != 0)								//如果请求的文件资源 存在
        {
            add_headers(m_file_stat.st_size);
            m_iv[0].iov_base = m_write_buf;							//第一个 iovec 指针指向响应报文的状态行的 缓冲区，长度指向  m_write_idx （状态行的大小）
            m_iv[0].iov_len = m_write_idx;
            m_iv[1].iov_base = m_file_address;						//第二个 iovec 指针指向 资源文件的mmap 返回的指向文件地址的指针， 长度指向文件大小
            m_iv[1].iov_len = m_file_stat.st_size;
            m_iv_count = 2;
            bytes_to_send = m_write_idx + m_file_stat.st_size;		//发送文件 从mmap 到 写缓冲区
            return true;
        }
        else														//如果请求的资源大小为0，则返回空白 html 文件
        {
            const char *ok_string = "<html><body></body></html>";
            add_headers(strlen(ok_string));
            if (!add_content(ok_string))
                return false;
        }
    }
    default:
        return false;
    }
    m_iv[0].iov_base = m_write_buf;									//除了 FILE_REQUEST（文件资源存在）状态外，其余状态只申请一个 iovec，指向报文响应状态行 缓冲区
    m_iv[0].iov_len = m_write_idx;
    m_iv_count = 1;
    bytes_to_send = m_write_idx;
    return true;
}
void http_conn::process()
{
    HTTP_CODE read_ret = process_read();									//调用 process_read 完成报文解析
    if (read_ret == NO_REQUEST)												//NO_REQUEST，表示请求不完整，需要继续接收请求数据
    {
        modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);					//重新注册并监听读事件
        return;
    }
    bool write_ret = process_write(read_ret);								//调用process_write完成报文响应
    if (!write_ret)
    {
        close_conn();
    }
    modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode);						//注册并监听写事件，当写事件满足时即向套接字中写回响应报文
}
